多个协程是并发执行的,因此不能使用类静态变量/全局变量保存协程上下文内容。使用局部变量是安全的,因为局部变量的值会自动保存在协程栈中,其他协程访问不到协程的局部变量。
$_array = [];
$serv->on("Request", function ($req, $resp){
global $_array;
//请求 /a(协程 1 )
if ($request->server['request_uri'] == '/a') {
$_array['name'] = 'a';
co::sleep(1.0);
echo $_array['name'];
$resp->end($_array['name']);
}
//请求 /b(协程 2 )
else {
$_array['name'] = 'b';
$resp->end();
}
});
发起2
个并发请求。
curl http://127.0.0.1:9501/a
curl http://127.0.0.1:9501/b
- 协程
1
中设置了全局变量$_array['name']
的值为a
- 协程
1
调用co::sleep
挂起 - 协程
2
执行,将$_array['name']
的值为b
,协程2结束 - 这时定时器返回,底层恢复协程1的运行。而协程1的逻辑中有一个上下文的依赖关系。当再次打印
$_array['name']
的值时,程序预期是a
,但这个值已经被协程2
所修改,实际结果却是b
,这样就造成了逻辑错误 - 同理,使用类静态变量
Class::$array
、全局对象属性$object->array
、其他超全局变量$GLOBALS
等,进行上下文保存在协程程序中是非常危险的。可能会出现不符合预期的行为。
- 可以使用一个
Context
类来管理协程上下文,在Context
类中,使用Coroutine::getUid
获取了协程ID
,然后隔离不同协程之间的全局变量 - 协程退出时清理上下文数据
use Swoole\Coroutine;
class Context
{
protected static $pool = [];
static function get($key)
{
$cid = Coroutine::getuid();
if ($cid < 0)
{
return null;
}
if(isset(self::$pool[$cid][$key])){
return self::$pool[$cid][$key];
}
return null;
}
static function put($key, $item)
{
$cid = Coroutine::getuid();
if ($cid > 0)
{
self::$pool[$cid][$key] = $item;
}
}
static function delete($key = null)
{
$cid = Coroutine::getuid();
if ($cid > 0)
{
if($key){
unset(self::$pool[$cid][$key]);
}else{
unset(self::$pool[$cid]);
}
}
}
}
$serv->on("Request", function ($req, $resp) {
if ($request->server['request_uri'] == '/a') {
Context::put('name', 'a');
co::sleep(1.0);
echo Context::get('name');
$resp->end(Context::get('name'));
//退出协程时清理
Context::delete('name');
} else {
Context::put('name', 'b');
$resp->end();
//退出协程时清理
Context::delete();
}
});